#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <assert.h>
#include <errno.h>
#include <termios.h>
#include <openssl/aes.h>
#include <openssl/md5.h>
#include <getopt.h>
#include <dirent.h>
#include <math.h>
#include <time.h>

#include "license.h"

#define ENCRYPT 1
#define DECRYPT 0

#define HOME "/opt/fusionstack"

static void usage()
{
        fprintf(stderr, "\nusage: lich.license [-vh]\n"
                "                             [-m sniffer ]\n"//[-o outfile]\n"
                "                             [-m list]\n"
                "\t-v --verbose         Show verbose message\n"
                "\t-h --help            Show this help\n"
                "\t-m --mode            sniffer: sniffer hardware info to outfile\n"
                "\t                        list: list license infomation\n"
                //"\t-o --outfile         Output file\n"
                "\n"
               );
}

static int __check_free_limit(int *free_limit)
{
        int ret;
        time_t now;
        uint32_t createtime;
        struct stat buf;
        char license[MAX_NAME_LEN];

        now = time(NULL);
        *free_limit = 0;

        snprintf(license, MAX_NAME_LEN, "%s/%s", HOME, "lic/ctime");
        ret = license_get_ctime(license, &createtime);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        snprintf(license, MAX_NAME_LEN, "%s/%s", HOME, "license");
        ret = stat(license, &buf);
        if (unlikely(ret)) {
                if (now - createtime <= FREE_LICENSE) {
                        *free_limit = createtime + FREE_LICENSE;
                }
        } else {
                if (buf.st_size == 0) {
                        if (now - createtime <= FREE_LICENSE) {
                                *free_limit = createtime + FREE_LICENSE;
                        }
                }
        }

        return 0;
err_ret:
        return ret;
}

static int __sniffer(char *path)
{
        int ret, fd=0;

        (void) path;
        //modify 'lich.license -m sniffer' output from file to terminal
        /*
        fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0200);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }
        */

        ret = license_sniffer(fd);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        //close(fd);

        return 0;
//err_close:
        //close(fd);
err_ret:
        return ret;
}

static void __decrypt_show(time_t limit, time_t free_limit, long capacity, int ret)
{
        char buf[MAXSIZE+1];
        struct tm *tm_limit, t;

        if (limit == -1) {
                printf("Permanent free license.\n");
        } else if (limit < free_limit) {
                limit = free_limit;
                capacity = -1;

                printf("Free license, ");
        } else if (limit != 0)
                printf("Valid license, ");

        if (limit != -1 && limit != 0) {
                tm_limit = localtime_r(&limit, &t);
                strftime(buf, MAXSIZE, "%F %T", tm_limit);
                printf("license expiration time: %s\n", buf);

                if (capacity == -1) {
                        printf("Infinite capacity.\n");
                } else {
                        printf("Permit capacity:%ldG.\n", capacity);
                }

                return 0;
        } else if (ret == 0) {
                if (capacity == -1) {
                        printf("Infinite capacity.\n");
                } else {
                        printf("Permit capacity:%ldG.\n", capacity);
                }

                return 0;
        }

        if (ret == EAGAIN)
                fprintf(stderr, "ERROR : %s, please retry later...\n", strerror(ret));
        else if (ret == ENOENT)
                printf("No license found.\n");
        else if (ret == ETIME)
                printf("License expired.\n");
        else if (ret == ENOSPC)
                printf("Excess capacity.\n");
        else if (ret == ENONET)
                printf("Node offline.\n");
        else
                printf("Invalid license.\n");
}

static void __decrypt_show_encrypt(time_t limit, time_t free_limit, long capacity, int ret)
{
        char data[MAXSIZE], line[MAXSIZE];
#if DECRYPT
        char tmp[MAXSIZE];
#endif
        struct tm *tm_limit, t;

        srand((int) time(NULL));

        if (limit == -1) {
                sprintf(line, "Permanent:%d", rand()%10);
                license_encrypt_data(line, data, MAXSIZE);
                printf("%s\n", data);
#if DECRYPT
                license_decrypt_data(data, tmp, MAXSIZE);
                printf("%s\n", tmp);
#endif
        } else if (limit < free_limit) {
                limit = free_limit;
                capacity = -1;

                sprintf(line, "Free:");
        } else if (limit != 0)
                sprintf(line, "Valid:");

        if (limit != -1 && limit != 0) {
                sprintf(line + strlen(line), "%ld", limit);
                license_encrypt_data(line, data, MAXSIZE);
                printf("%s\n", data);
#if DECRYPT
                license_decrypt_data(data, tmp, MAXSIZE);
                printf("%s\n", tmp);
#endif

                if (capacity == -1) {
                        sprintf(line, "Infinite:%d", rand()%10);
                } else {
                        sprintf(line, "Permit:%ld", capacity);
                }

                license_encrypt_data(line, data, MAXSIZE);
                printf("%s\n", data);
#if DECRYPT
                license_decrypt_data(data, tmp, MAXSIZE);
                printf("%s\n", tmp);
#endif

                return 0;
        } else if (ret == 0) {
                if (capacity == -1) {
                        sprintf(line, "Infinite:%d", rand()%10);
                } else {
                        sprintf(line, "Permit:%ld", capacity);
                }

                license_encrypt_data(line, data, MAXSIZE);
                printf("%s\n", data);
#if DECRYPT
                license_decrypt_data(data, tmp, MAXSIZE);
                printf("%s\n", tmp);
#endif

                return 0;
        }

        if (ret == EAGAIN)
                fprintf(stderr, "ERROR : %s, please retry later...\n", strerror(ret));
        else if (ret == ENOENT)
                sprintf(line, "No license");
        else if (ret == ETIME)
                sprintf(line, "License expired");
        else if (ret == ENOSPC)
                sprintf(line, "Excess capacity");
        else if (ret == ENONET)
                sprintf(line, "Node offline");
        else
                sprintf(line, "Invalid license");

        license_encrypt_data(line, data, MAXSIZE);
        printf("%s\n", data);
#if DECRYPT
        license_decrypt_data(data, tmp, MAXSIZE);
        printf("%s\n", tmp);
#endif
}

static int __decrypt()
{
        int ret, free_limit = 0, fd = 0;
        long capacity = 0;
        time_t limit = 0;
        char license_path[MAX_PATH_LEN];

        ret = __check_free_limit(&free_limit);
        if(ret)
                GOTO(err_ret, ret);

        sprintf(license_path, "%s/license", HOME);
        ret = license_check(license_path, &capacity, &limit);
        if (unlikely(ret))
                GOTO(err_ret, ret);

err_ret:
#if ENCRYPT
        __decrypt_show_encrypt(limit, free_limit, capacity, ret);
#else
        __decrypt_show(limit, free_limit, capacity, ret);
#endif

        return ret;
}

int main(int argc, char *argv[])
{
        int ret, verbose = 0;
        char c_opt, *outfile = NULL;
        enum mode_t {
                MODE_NULL,
                MODE_SNIFFER,
                MODE_LIST,
        } mode = MODE_NULL;

        (void) verbose;

        while (1) {
                int option_index = 0;

                static struct option long_options[] = {
                        { "verbose", 0, 0, 'v' },
                        { "help",    0, 0, 'h' },
                        { "mode",    1, 0, 'm' },
                        //{ "outfile", 1, 0, 'o' },
                        { 0, 0, 0, 0 },
                };

                c_opt = getopt_long(argc, argv, "vhm:", long_options, &option_index);
                if (c_opt == -1)
                        break;

                switch (c_opt) {
                case 'v':
                        verbose = 1;
                        break;
                case 'h':
                        usage();
                        exit(0);
                case 'm':
                        if (!strcmp(optarg, "sniffer"))
                                mode = MODE_SNIFFER;
                        else if (!strcmp(optarg, "list"))
                                mode = MODE_LIST;
                        break;
                /*
                case 'o':
                        outfile = optarg;
                        break;
                */
                default:
                        usage();
                        EXIT(EINVAL);
                }
        }

        switch (mode) {
        case MODE_SNIFFER:
                /*
                if (!outfile) {
                        fprintf(stderr, "outfile must be specified when sniffer\n");
                        EXIT(EINVAL);
                }
                */

                ret = __sniffer(outfile);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case MODE_LIST:
                ret = __decrypt();
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case MODE_NULL:
        default:
                usage();
                EXIT(EINVAL);
        }

        return 0;
err_ret:
        return ret;
}
